﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace PortalCurriculo.Util.Translation
{
    /// <summary>
    /// Class responsible for translate ASPX pages
    /// </summary>
    public static class Translator
    {
        #region Public control variables

        /// <summary>
        /// Indicates where are placed the resx files
        /// </summary>
        public static string FolderResource = "Resources";

        /// <summary>
        /// Default language, this will be used if there is no support for the browser language or if an error occured.
        /// </summary>
        public static CultureInfo DefaultLanguage = new CultureInfo("EN-US");

        /// <summary>
        /// Resource category (resource name)
        /// </summary>
        public static string Category { get; set; }

        /// <summary>
        /// Browser language
        /// </summary>
        public static CultureInfo Language { get; set; }

        #endregion

        #region Private control variables

        /// <summary>
        /// Path where located de resource files
        /// </summary>
        private static string StringResourcePath { get; set; }

        //@"C:\Users\nathan\Desktop\TesteInternacionalizacao\TesteInternacionalizacao\Resources\[Cultura]\[Categoria].resx";

        /// <summary>
        /// Stream containing files
        /// </summary>
        private static Stream StreamResourceFile { get; set; }

        /// <summary>
        /// Contain values extracted from resource file
        /// </summary>
        private static ResXResourceReader ResourceReader { get; set; }

        /// <summary>
        /// Asp.net page name without extension
        /// </summary>
        private static string PageName { get; set; }

        #endregion

        #region Methods for component translation

        /// <summary>
        /// Translate all components on the screen
        /// </summary>
        /// <param name="page">Web page</param>
        public static void TranslatePage(Page page)
        {
            if (Category == null)
                Category = "Components";

            AbrirArquivoResource(page);

            TranslateControls(PageName, page.Controls);

            CloseStream();
        }

        private static void TranslateControls(string pagePath, ControlCollection controls)
        {
            foreach (Control controlePagina in controls)
            {
                if (controlePagina.GetType().BaseType.BaseType != null &&
                    controlePagina.GetType().BaseType.BaseType.FullName.Equals("System.Web.UI.UserControl"))
                {
                    string userControlPath =
                        (controlePagina).TemplateControl.AppRelativeVirtualPath.Replace(@"~/", "").Replace(".ascx", "");
                    TranslateControls(userControlPath, controlePagina.Controls);
                }
                else
                    //TranslateControls(PageName, controlePagina.Controls);
                    TranslateControls(pagePath, controlePagina.Controls);

                if (controlePagina.ID != null)
                {
                    List<DictionaryEntry> dicionarioResource = (from DictionaryEntry dicionario in ResourceReader
                                                                where
                                                                    dicionario.Key.ToString().StartsWith("*." +
                                                                                                         controlePagina.
                                                                                                             ID)
                                                                    ||
                                                                    dicionario.Key.ToString().StartsWith(pagePath + "." +
                                                                                                         controlePagina.
                                                                                                             ID)
                                                                select dicionario).ToList();

                    if (dicionarioResource.Count > 0)
                        LocalizarComponente(dicionarioResource, controlePagina);
                }
            }
        }

        /// <summary>
        /// Localiza o componente na tela e o traduz, retornando true caso tenha encontrado-o
        /// </summary>
        /// <param name="dictionaryEntries">DictionaryEntry contendo os valores do resource</param>
        /// <param name="controlePagina">Controle em que está se procurando</param>
        /// <returns>True caso tenha encontrado o componente</returns>
        private static void LocalizarComponente(IEnumerable<DictionaryEntry> dictionaryEntries, Control controlePagina)
        {
            foreach (DictionaryEntry dictionaryEntry in dictionaryEntries)
            {
                List<string> niveisChave = SepararNiveisChave(dictionaryEntry.Key.ToString());

                if (niveisChave[1] == controlePagina.ID)
                    TraduzirControle(dictionaryEntry.Value.ToString(), niveisChave, ref controlePagina);
            }
        }

        /// <summary>
        /// Traduz o controle de acordo com o valor do resource
        /// </summary>
        /// <param name="valorResource">Valor do resource traduzido</param>
        /// <param name="niveisChave">Níveis de chave do resource</param>
        /// <   param name="controle">Controle que será traduzido</param>
        private static void TraduzirControle(string valorResource, List<string> niveisChave, ref Control controle)
        {
            Type tipo = controle.GetType();

            //Traduzir o texto de controles do tipo list control com valores fixos
            if (IsTranslatedListControl(controle, tipo, niveisChave, valorResource)) return;

            //apenas para alterar a propriedade header text de uma coluna em um grid view
            if (IsTranslateGridView(controle, tipo, niveisChave, valorResource)) return;

            PropertyInfo propertyInfo = tipo.GetProperty(niveisChave.Count > 2 ? niveisChave[2] : "Text");

            if (valorResource.StartsWith("[") && valorResource.EndsWith("]"))
            {
                Category = null;
                valorResource = TranslateVariable(valorResource, controle.Page);
            }

            if (propertyInfo != null)
                propertyInfo.SetValue(controle, valorResource, null);
        }

        private static bool IsTranslateGridView(Control controle, Type tipo, List<string> niveisChave,
                                                string valorResource)
        {
            PropertyInfo propertyInfo;
            if (tipo.FullName.Equals("System.Web.UI.WebControls.GridView", StringComparison.OrdinalIgnoreCase))
            {
                if (niveisChave[2].Equals("Columns", StringComparison.OrdinalIgnoreCase))
                {
                    var grdControl = (GridView) controle;

                    int indice;
                    if (int.TryParse(niveisChave[3], out indice))
                    {
                        if (indice < grdControl.Columns.Count)
                            grdControl.Columns[indice].HeaderText = valorResource;
                    }
                }
                else if (niveisChave.Count > 2)
                {
                    propertyInfo = tipo.GetProperty(niveisChave[2]);

                    propertyInfo.SetValue(controle, valorResource, null);
                }

                return true;
            }

            return false;
        }

        private static bool IsTranslatedListControl(Control controle, Type tipo, List<string> niveisChave,
                                                    string valorResource)
        {
            if (tipo.BaseType.FullName.Equals("System.Web.UI.WebControls.ListControl",
                                              StringComparison.OrdinalIgnoreCase))
            {
                var lsControl = (ListControl) controle;
                int indice;

                if (int.TryParse(niveisChave[2], out indice))
                {
                    if (indice < lsControl.Items.Count)
                        lsControl.Items[indice].Text = valorResource;
                }

                return true;
            }

            return false;
        }

        #endregion

        #region Methods for message translation

        /// <summary>
        /// Translate general messages in application.
        /// </summary>
        /// <param name="message">Message to be translated</param>
        /// <param name="pagePath"></param>
        /// <returns>Translated message</returns>
        public static string TranslateMessage(object message, string pagePath)
        {
            if (Category == null)
                Category = "Messages";

            PageName = pagePath.Replace(@"~/", "").Replace(".aspx", "");

            AbrirArquivoResource();
            string mensagemTraduzida = null;

            IEnumerable<DictionaryEntry> dicionarioResource = from DictionaryEntry dicionario in ResourceReader
                                                              where (dicionario.Key.ToString().StartsWith("*.")
                                                                     ||
                                                                     dicionario.Key.ToString().StartsWith(PageName + "."))
                                                                    && dicionario.Key.ToString().EndsWith("." + message)
                                                              select dicionario;

            if (dicionarioResource.Count() > 0)
                mensagemTraduzida = dicionarioResource.First().Value.ToString();

            CloseStream();

            return mensagemTraduzida;
        }

        /// <summary>
        /// Translate general messages in application.
        /// </summary>
        /// <param name="message">Message to be translated</param>
        /// <param name="page">Web page</param>
        /// <returns>Translated message</returns>
        public static string TranslateMessage(object message, Page page)
        {
            if (Category == null)
                Category = "Messages";

            AbrirArquivoResource(page);


            string mensagemTraduzida = null;

            IEnumerable<DictionaryEntry> dicionarioResource = from DictionaryEntry dicionario in ResourceReader
                                                              where (dicionario.Key.ToString().StartsWith("*.")
                                                                     ||
                                                                     dicionario.Key.ToString().StartsWith(PageName + "."))
                                                                    && dicionario.Key.ToString().EndsWith("." + message)
                                                              select dicionario;

            if (dicionarioResource.Count() > 0)
                mensagemTraduzida = dicionarioResource.First().Value.ToString();

            CloseStream();

            return mensagemTraduzida;
        }

        #endregion

        #region Methods for variables translation

        /// <summary>
        /// Translate any variable from resource file
        /// </summary>
        /// <param name="variable">Variable from resource</param>
        /// <param name="page">Web page</param>
        /// <returns>Translated variable</returns>
        public static string TranslateVariable(string variable, Page page)
        {
            if (Category == null)
                Category = "Variables";

            Stream stream = AbrirStream(page);
            var resourceReaderVariavel = new ResXResourceReader(stream);

            string variavelTraduzida = null;

            IEnumerable<DictionaryEntry> dicionarioResource = from DictionaryEntry dicionario in resourceReaderVariavel
                                                              where ((dicionario.Key.ToString().StartsWith("*.")
                                                                      || dicionario.Key.ToString().StartsWith(PageName + "."))
                                                                      && dicionario.Key.ToString().EndsWith("." + variable))
                                                                      || dicionario.Key.ToString().Equals(variable)
                                                              select dicionario;

            if (dicionarioResource.Count() > 0)
                variavelTraduzida = dicionarioResource.First().Value.ToString();

            stream.Close();
            //FecharArquivo();
            Category = null;
            StringResourcePath = null;

            return variavelTraduzida;
        }

        #endregion

        #region Methods for Control translation

        public static void TranslateControl(Page page, Control control)
        {
            if (Category == null)
                Category = "Components";

            AbrirArquivoResource(page);

            if (control.ID != null)
            {
                List<DictionaryEntry> dicionarioResource = (from DictionaryEntry dicionario in ResourceReader
                                                            where
                                                                dicionario.Key.ToString().StartsWith("*." + control.ID)
                                                                ||
                                                                dicionario.Key.ToString().StartsWith(PageName + "." +
                                                                                                     control.ID)
                                                            select dicionario).ToList();

                if (dicionarioResource.Count > 0)
                    LocalizarComponente(dicionarioResource, control);
            }

            CloseStream();
        }

        #endregion

        #region Métodos genéricos para identificação de idioma e manipulação de arquivo de resource/Generic methods for language identification and file resource manipulation

        /// <summary>
        /// Abre o arquivo de resource de acordo com o idioma selecionado
        /// </summary>
        /// <param name="pagina"></param>
        private static void AbrirArquivoResource(Page pagina)
        {
            StreamResourceFile = AbrirStream(pagina);
            ResourceReader = new ResXResourceReader(StreamResourceFile);
        }

        /// <summary>
        /// Abre o arquivo de resource de acordo com o idioma selecionado
        /// </summary>
        private static void AbrirArquivoResource()
        {
            StreamResourceFile = AbrirStream();
            ResourceReader = new ResXResourceReader(StreamResourceFile);
        }


        /// <summary>
        /// Abre um arquivo de resource e retorna o mesmo
        /// </summary>
        /// <param name="pagina">Página Web</param>
        /// <returns>Stream aberto com o arquivo de resource</returns>
        private static Stream AbrirStream(Page pagina)
        {
            if (StringResourcePath == null)
            {
                //string caminhoFisico = Path.GetDirectoryName(Path.GetDirectoryName(pagina.MapPath(pagina.AppRelativeVirtualPath)));
                StringResourcePath = @"[ProjetoResource]\[Cultura]\[Categoria].resx";
            }

            IdentificarIdiomaBrowser(pagina);

            string arquivoResource = IdentificarArquivoResource();
            PageName = pagina.TemplateControl.AppRelativeVirtualPath.Replace(@"~/", "").Replace(".aspx", "");

            Stream stream = File.Open(arquivoResource, FileMode.Open, FileAccess.Read);

            return stream;
        }

        /// <summary>
        /// Abre um arquivo de resource e retorna o mesmo
        /// </summary>
        /// <param name="pagina">Página Web</param>
        /// <returns>Stream aberto com o arquivo de resource</returns>
        private static Stream AbrirStream()
        {
            if (StringResourcePath == null)
            {
                //string caminhoFisico = Path.GetDirectoryName(Path.GetDirectoryName(pagina.MapPath(pagina.AppRelativeVirtualPath)));
                StringResourcePath = @"[ProjetoResource]\[Cultura]\[Categoria].resx";
            }

            string arquivoResource = IdentificarArquivoResource();

            Stream stream = File.Open(arquivoResource, FileMode.Open, FileAccess.Read);

            return stream;
        }

        /// <summary>
        /// Separa em um list os níveis criados na chave do resource
        /// </summary>
        /// <param name="keyResource">Chave com todos os níveis</param>
        /// <returns>List com os níveis separados</returns>
        private static List<string> SepararNiveisChave(string keyResource)
        {
            var separador = new char[1];
            separador[0] = '.';

            List<string> niveis = keyResource.Split(separador).ToList();

            if (niveis.Count.Equals(0)) throw new Exception("Chave de Resource inválida");

            return niveis;
        }

        /// <summary>
        /// Substitui as variáveis do caminho do arquivo de recurso
        /// </summary>
        /// <param name="cultura">Cultura a se substituida</param>
        /// <returns>Caminho com as variáveis substituídas</returns>
        private static string SubstituirVariaveis(string cultura)
        {
            string caminhoResource = StringResourcePath;
            caminhoResource = SubstituirVariaveis("[Cultura]", cultura, caminhoResource);
            caminhoResource = SubstituirVariaveis("[Categoria]", Category, caminhoResource);
            caminhoResource = SubstituirVariaveis("[ProjetoResource]", FolderResource, caminhoResource);
            return caminhoResource;
        }

        /// <summary>
        /// Substitui uma variável específica do caminho do arquivo de recurso
        /// </summary>
        /// <param name="variavel">Variável a ser substituída</param>
        /// <param name="valor">Valor substituidor</param>
        /// <param name="texto">Texto com as variáveis a serem substituídas</param>
        /// <returns>Caminho com a variável substituída</returns>
        private static string SubstituirVariaveis(string variavel, string valor, string texto)
        {
            return texto.Replace(variavel, valor);
        }

        /// <summary>
        /// Fecha o stream do arquivo de resource
        /// </summary>
        private static void CloseStream()
        {
            if (StreamResourceFile.CanRead)
                StreamResourceFile.Close();

            Category = null;
            StringResourcePath = null;
            PageName = null;
        }

        /// <summary>
        /// Identifica o idioma padrão do browser e procura o resource, caso não haja, procurará o idioma pai (sem especificar o país)
        /// e caso não encontre o idioma pai, retornará o idioma definido com padrão da aplicação. Retorna o caminho do resource.
        /// </summary>
        /// <returns>Caminho do resource</returns>
        private static string IdentificarArquivoResource()
        {
            //List<string> idiomas = pagina.Request.UserLanguages.ToList();
            string arquivoResource = SubstituirVariaveis(Language.ToString());

            if (File.Exists(arquivoResource) == false)
            {
                arquivoResource = SubstituirVariaveis(Language.Parent.ToString());

                if (File.Exists(arquivoResource) == false)
                {
                    arquivoResource = SubstituirVariaveis(DefaultLanguage.ToString());
                }
            }
            return arquivoResource;
        }


        /// <summary>
        /// Identifica o idioma do browser
        /// </summary>
        /// <param name="pagina"></param>
        private static void IdentificarIdiomaBrowser(Page pagina)
        {
            if (Language == null)
            {
                try
                {
                    Language = new CultureInfo(pagina.Request.UserLanguages[0]);
                }
                catch (Exception)
                {
                    Language = new CultureInfo(DefaultLanguage.ToString());
                }
            }
        }

        #endregion

        #region Methods for database translation

        #endregion
    }
}